package cn.demoncat.util.pay.ceb;

import cebenc.softenc.SoftEncGM;
import cn.demoncat.util.exception.BizErrorRuntimeException;
import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.exception.RestRuntimeException;
import cn.demoncat.util.http.HttpUtil;
import cn.demoncat.util.http.entity.HttpRequest;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.*;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StatusConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.MapSo;
import cn.demoncat.util.lang.entity.Pager;
import cn.demoncat.util.pay.ceb.config.CebProperties;
import cn.demoncat.util.pay.ceb.constant.CebApi;
import cn.demoncat.util.pay.ceb.constant.CebLogs;
import cn.demoncat.util.pay.ceb.entity.*;
import cn.demoncat.util.pay.constant.PayMsgs;
import cn.demoncat.util.web.config.AppProperties;
import cn.demoncat.util.xml.XmlUtil;
import cn.demoncat.util.xml.entity.XStreamClassAliasParam;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 光大银行资金监管系统外围接口
 *
 * @author 延晓磊
 * @since 2022年01月20日
 */
@Component("cebUtil")
public class CebUtil {

    @Resource
    private CebProperties cebProperties;
    @Resource
    private AppProperties appProperties;

    // 光大银行密钥工具
    private SoftEncGM softEncGM;

    // 字符集
    private static final Charset CHARSET = CharsetConstant.GBK_CHARSET;

    // 银行名称
    public static final String BANK_NAME = "中国光大银行";

    private static final Logger LOG = LoggerFactory.getLogger(CebUtil.class);

    /**
     * 付款信息
     *
     * 注意：中文配置要用Unicode编码
     *
     * @param memAccount    会员子账户
     * @return
     *
     * @author 延晓磊
     * @since 2022年02月24日
     */
    public FundInfo fundInfo(String memAccount){
        FundInfo info =  new FundInfo();
        info.setBankNum(cebProperties.getInstAccount());
        info.setPayeeName(cebProperties.getInstName());
        info.setPayeeNum(memAccount);
        return info;
    }

    /**
     * 查询银行及行号（总行）
     *
     * @param accountNumber 银行卡号（个人）
     * @return 银行及行号，查询不到（公户、小银行）返回null
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public QueryBankResponseParam.ResponseBody queryBank(String accountNumber){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"accountNumber",accountNumber);
        // 请求
        try {
           return request(CebApi.QueryBank, XmlUtil.toXml(map), QueryBankResponseParam.class).getBody();
        }catch (BizErrorRuntimeException e){
            if (e.getMessage().contains("查询不到")) {
                // 无数据
                return null;
            }else{
                throw  e;
            }
        }
    }

    /**
     * 查询资金余额
     *
     * @param virAccType    账户类型：0-虚拟汇总户、1-虚拟收入户、2-虚拟利息户、3-支付调节户、5-资金暂挂户、7-自有资金回补户、99-会员资金余额
     * @param memNum        会员编号（类型为99时必填）
     * @param queryDate     日期：yyyyMMdd；为空时查询实时
     *
     * @return {balance, useableBalance}
     *
     * @author 延晓磊
     * @since 2022年02月23日
     */
    public AmountResponseParam.ResponseBody queryAccountBalance(String virAccType, String memNum, String queryDate){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"virAccType",virAccType,"memNum",memNum,"queryDate",queryDate);
        // 请求
        return request(CebApi.QueryAccountBalance, XmlUtil.toXml(map), AmountResponseParam.class).getBody();
    }

    /**
     * 查询资金明细
     *
     * 场景：出金查询、入金查询、资金暂挂明细查询、交易明细查询
     *
     * @param memNum        会员编码（虚拟账户类型为99时必填）
     * @param mark          标识：0全部,1出金,2入金,3交易明细（会员）,4资金暂挂（商户）
     * @param virAccType    虚拟账户类型：0-虚拟汇总户、1-虚拟收入户、2-虚拟利息户、3-支付调节户、5-资金暂挂户、7-自有资金回补户、99-会员资金余额
     * @param startNum      起始笔数，从1开始
     * @param queryNum      查询笔数，最大99
     * @param startDate     开始时间：yyyyMMdd
     * @param endDate       结束时间：yyyyMMdd，相距小于3个月
     * @param obsolete      true查询已作废明细，false不包含已作废明细
     * @param asc           true按照账务处理时间正序排列，false按照账务处理时间倒序排列
     * @return  {totalCount,list}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryAccountLogResponseParam.ResponseBody.Data> queryAccountLog(String memNum, String mark, String virAccType, Integer startNum, Integer queryNum, String startDate, String endDate, boolean obsolete, boolean asc){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "memNum", StringUtils.trimToEmpty(memNum), "mark", StringUtil.toDefault(mark, "0"), "virAccType", StringUtil.toDefault(virAccType,"0"));
        map.add("startNum", startNum, "queryNum", queryNum, "startDate", startDate, "endDate", endDate);
        map.add("filed1", obsolete ? "fcsis" : StringConstant.EMPTY);
        map.add("filed2", asc ? "fcsasc" : StringConstant.EMPTY);
        // 请求
        QueryAccountLogResponseParam response;
        try {
            response = request(CebApi.QueryAccountLog, XmlUtil.toXml(map), QueryAccountLogResponseParam.class);
        }catch (BizErrorRuntimeException e){
            if (e.getMessage().contains("查询无记录")) {
                // 无数据
                return new Pager<>();
            }else{
                throw  e;
            }
        }
        // 转换列表
        Pager<QueryAccountLogResponseParam.ResponseBody.Data> page = new Pager<>();
        page.setTotalCount(response.getBody().getTotalNum());
        page.setList(response.getBody().getFrame());
        return page;
    }

    /**
     * 查询资金明细分页
     *
     * 场景：出金查询、入金查询、资金暂挂明细查询、交易明细查询
     *
     * @param memNum        会员编码（虚拟账户类型为99时必填）
     * @param mark          标识：0全部,1出金,2入金,3交易明细（会员）,4资金暂挂（商户）
     * @param virAccType    虚拟账户类型：0-虚拟汇总户、1-虚拟收入户、2-虚拟利息户、3-支付调节户、5-资金暂挂户、7-自有资金回补户、99-会员资金余额
     * @param pageNum      页号
     * @param pageSize      页长，最大99
     * @param startDate     开始时间：yyyy-MM-dd
     * @param endDate       结束时间：yyyy-MM-dd，相距小于3个月
     * @param obsolete      true查询已作废明细，false不包含已作废明细
     * @param asc           true按照账务处理时间正序排列，false按照账务处理时间倒序排列
     * @return  pager
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryAccountLogResponseParam.ResponseBody.Data> queryAccountLogPage(String memNum, String mark, String virAccType, Integer pageNum, Integer pageSize, String startDate, String endDate, boolean obsolete, boolean asc) {
        // 初始值
        if (IntegerUtil.isEmpty(pageSize) || pageSize > 99) {
            // 页长：最大99
            pageSize = 99;
        }
        if (IntegerUtil.isEmpty(pageNum)) {
            pageNum = 1;
        }
        LocalDate startTime;
        LocalDate endTime;
        if (StringUtils.isBlank(endDate)) {
            endTime = LocalDate.now().plusDays(1);
        } else {
            endTime = LocalDateUtil.toLocalDate(endDate);
        }
        if (StringUtils.isBlank(startDate)) {
            // 跨度3个月
            startTime = endTime.minusMonths(3);
        } else {
            startTime = LocalDateUtil.toLocalDate(startDate);
            if (LocalDateUtil.betweenMonth(startTime, endTime) > 3) {
                throw new BizRuntimeException("日期跨度不能大于3个月");
            }
        }
        // 查询
        Pager<QueryAccountLogResponseParam.ResponseBody.Data> page = queryAccountLog(memNum, mark, virAccType, (pageNum - 1) * pageSize + 1, pageSize, LocalDateUtil.toNum(startTime), LocalDateUtil.toNum(endTime), obsolete, asc);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        page.setPageCount(Pager.getPageCount(page.getTotalCount(), pageSize));
        return page;
    }

    /**
     * 查询会员资金明细
     *
     * 场景：出金查询、入金查询、交易明细。支持查询最近12个月内的明细
     *
     * @param memNum        会员编码
     * @param mark          标识：0全部,1出金,2入金,3交易明细
     * @param startNum      起始笔数，从1开始
     * @param queryNum      查询笔数，最大99
     * @param startDate     开始时间：yyyyMMdd
     * @param endDate       结束时间：yyyyMMdd，相距小于3个月
     * @param asc           true按照账务处理时间正序排列，false按照账务处理时间倒序排列
     * @return  {totalCount,list}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryMemberAccountLogResponseParam.ResponseBody.Data> queryMemberAccountLog(String memNum, String mark, Integer startNum, Integer queryNum, String startDate, String endDate, boolean asc){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "memNum", StringUtils.trimToEmpty(memNum), "mark", StringUtil.toDefault(mark, "0"));
        map.add("startNum", startNum, "queryNum", queryNum, "startDate", startDate, "endDate", endDate);
        map.add("sortMark", StatusConstant.Common.check(asc));
        // 请求
        QueryMemberAccountLogResponseParam response;
        try {
            response = request(CebApi.QueryMemberAccountLog, XmlUtil.toXml(map), QueryMemberAccountLogResponseParam.class);
        }catch (BizErrorRuntimeException e){
            if (e.getMessage().contains("查询无记录")) {
                // 无数据
                return new Pager<>();
            }else{
                throw  e;
            }
        }
        // 转换列表
        Pager<QueryMemberAccountLogResponseParam.ResponseBody.Data> page = new Pager<>();
        page.setTotalCount(response.getBody().getTotalNum());
        page.setList(response.getBody().getFrame());
        return page;
    }

    /**
     * 查询会员资金明细
     *
     * 场景：出金查询、入金查询、交易明细。支持查询最近12个月内的明细
     *
     * @param memNum        会员编码
     * @param mark          标识：0全部,1出金,2入金,3交易明细
     * @param pageNum      页号
     * @param pageSize      页长，最大99
     * @param startDate     开始时间：yyyy-MM-dd
     * @param endDate       结束时间：yyyy-MM-dd，相距小于3个月
     * @param asc           true按照账务处理时间正序排列，false按照账务处理时间倒序排列
     * @return  {totalCount,list}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryMemberAccountLogResponseParam.ResponseBody.Data> queryMemberAccountLogPage(String memNum, String mark, Integer pageNum, Integer pageSize, String startDate, String endDate, boolean asc){
        // 初始值
        if (IntegerUtil.isEmpty(pageSize) || pageSize > 99) {
            // 页长：最大99
            pageSize = 99;
        }
        if (IntegerUtil.isEmpty(pageNum)) {
            pageNum = 1;
        }
        LocalDate startTime;
        LocalDate endTime;
        if (StringUtils.isBlank(endDate)) {
            endTime = LocalDate.now().plusDays(1);
        } else {
            endTime = LocalDateUtil.toLocalDate(endDate);
        }
        if (StringUtils.isBlank(startDate)) {
            // 跨度3个月
            startTime = endTime.minusMonths(3);
        } else {
            startTime = LocalDateUtil.toLocalDate(startDate);
            if (LocalDateUtil.betweenMonth(startTime, endTime) > 3) {
                throw new BizRuntimeException("日期跨度不能大于3个月");
            }
        }
        if (LocalDateUtil.betweenMonth(LocalDate.now(), startTime) > 12) {
            throw new BizRuntimeException("只能查询近12个月的数据");
        }
        // 查询
        Pager<QueryMemberAccountLogResponseParam.ResponseBody.Data> page = queryMemberAccountLog(memNum, mark, (pageNum - 1) * pageSize + 1, pageSize, LocalDateUtil.toNum(startTime), LocalDateUtil.toNum(endTime), asc);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        page.setPageCount(Pager.getPageCount(page.getTotalCount(), pageSize));
        return page;
    }

    /**
     * 会员账户管理
     *
     * @param param
     *
     * @return {memNum:会员编号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public MemberManageResponseParam manageMember(MemberManageRequestParam param){
        // 参数
        MapSo map = MapSo.getInstance("opeType",param.getOptType(),"memNum",param.getMemNum(),"memName",param.getMemName(),"memType","0","busiNum",cebProperties.getInstId(),"regisName",param.getRegisName(),"identiType","b","identiNum",param.getIdentiNum(),"REALNAME",param.getLegalName(),"IDNO",param.getLegalIdCard());
        if (CollectionUtils.isNotEmpty(param.getAccounts())) {
            List<MapSo> accounts = new ArrayList<>(param.getAccounts().size());
            for (MemberManageRequestParam.Account account : param.getAccounts()) {
                accounts.add(MapSo.getInstance("opeAccType", account.getOpeAccType(), "bindOpenBank", account.getBindOpenBank(), "bindOpenOrgNum", account.getBindOpenOrgNum(), "bindOpenBankName", account.getBindOpenBankName(), "bindAccNum", account.getBindAccNum(), "bindAccName", account.getBindAccName(), "isCrossLine", account.getBindOpenBank().startsWith(BANK_NAME) ? "01":"02"));
            }
            map.put("frame", accounts);
        }
        // 请求
        return request(CebApi.ManageMember, XmlUtil.toXml(map), MemberManageResponseParam.class);
    }

    /**
     * 会员账户查询
     *
     * @param memNum    会员编号（与regisName二选一）
     * @param regisName    会员在商户的唯一标识
     *
     * @param {memNum:会员编号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public MemberQueryResponseParam.ResponseBody queryMember(String memNum, String regisName){
        // 参数
        MapSo map = MapSo.getInstance("busiNum",cebProperties.getInstId(), "memNum", memNum, "regisName",regisName);
        // 请求
        MemberQueryResponseParam.ResponseBody body = request(CebApi.QueryMember, XmlUtil.toXml(map), MemberQueryResponseParam.class).getBody();
        if (body.getFrame() != null && body.getFrame().size() == 1 && StringUtils.isBlank(body.getFrame().get(0).getBindAccNum())) {
            // 清理空数据
            body.getFrame().clear();
        }
        return body;
    }

    /**
     * 清分暂挂资金（未识别的入金）
     *
     * 场景：企业转账到商户实体账户，因未绑定会员账户而登记到暂挂资金，这时人工将其清分到企业的会员虚拟账户
     *
     * @param fcsSerialNum  资金监管流水号
     * @param busiSerialNum  商户流水号
     * @param brief  摘要：入账类型为1时填写会员号；为2时按照需求填写以下字段 -- CEB04表示利息收入、CEB02-自有资金户、CEB04-利息收入、CEB06-手续费收入
     * @param depositType   入账类型：1-会员入账、2-商户入账
     *
     * @return {fcsSerialNum 资金监管流水号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam clearTempFund(String fcsSerialNum, String busiSerialNum, String brief, String depositType){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"fcsSerialNum",fcsSerialNum,"busiSerialNum",busiSerialNum,"brief",brief,"depositType",depositType);
        // 请求
        return request(CebApi.ClearTempFund, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 退还暂挂资金（未识别的入金）
     *
     * 场景：企业转账到商户实体账户，因未绑定会员账户而登记到暂挂资金，这时人工将其退还
     *
     * @param busiSerialNum 商户流水号
     * @param oldFcsSerialNum 原资金监管系统流水号
     * @param brief     摘要
     *
     * @return {fcsSerialNum 资金监管流水号, amount 金额, coreTelSerialNum 核心流水号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam returnTempFund(String busiSerialNum, String oldFcsSerialNum, String brief){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"busiSerialNum",busiSerialNum,"oldFcsSerialNum",oldFcsSerialNum,"brief",brief);
        // 请求
        return request(CebApi.ReturnTempFund, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 会员账户冻结/解冻
     *
     * @param fromAccountName    会员编号
     * @param busiSerialNum    商户流水号：12位，全局唯一
     * @param opeType    业务类型：0-冻结、1-解冻
     * @param amount    金额：15,2
     * @param orgBusiSerialNum    冻结时的商户流水号(解冻时必填)
     *
     * @return {fcsSerialNum 资金监管流水号, resultCode 处理标记：1-成功、2-失败、3-未处理、0-未明}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam freeze(String fromAccountName, String busiSerialNum, String opeType, BigDecimal amount, String orgBusiSerialNum){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "fromAccountName",fromAccountName, "busiSerialNum", busiSerialNum, "opeType", opeType, "amount",amount,"orgBusiSerialNum", orgBusiSerialNum);
        // 请求
        return request(CebApi.Freeze, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 会员间交易（支付/解冻+支付）、平台收取（会员账户>平台交易账户）、平台退款（平台交易账户>会员账户）
     *
     * @param busiSerialNum    商户流水号：12位，全局唯一
     * @param orderNum          商户订单号：30位，全局唯一
     * @param fromAccountName    付款会员编号。空表示平台，即平台（交易账户）退款，类型为04（支付），toAccountName须为企业会员编号
     * @param amount    金额：15,2
     * @param toAccountName    收款会员编号。空表示平台，即平台（交易账户）收取，类型为19（解冻+支付）
     * @param filed2    冻结时的商户流水号：toAccountName = null 即收款方为平台时必填
     * @param filed1   摘要
     * @param otherFee  平台收取的手续费
     *
     * @return {fcsSerialNum 资金监管流水号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam pay(String busiSerialNum, String orderNum, String fromAccountName,  BigDecimal amount, String toAccountName, String filed2, String filed1, BigDecimal otherFee){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "busiSerialNum", busiSerialNum, "orderNum",orderNum, "fromAccountName",StringUtils.defaultIfBlank(fromAccountName, cebProperties.getTradeAccount()), "amount",amount,"toAccountName", StringUtils.defaultIfBlank(toAccountName, cebProperties.getTradeAccount()), "type", StringUtils.isBlank(fromAccountName) ? "04" : "19", "filed2", filed2, "filed1", filed1, "fee", 0, "otherFee", DecimalUtil.toEmpty(otherFee));
        // 请求
        return request(CebApi.Pay, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 会员提现
     *
     * @param memNum    会员编号
     * @param busiSerialNum    商户流水号：12位，全局唯一
     * @param outAmount    金额：15,2
     * @param jointLineNum    收款账户对应清算行联行号：超网通道=总行联行行号，大小额通道=支行联行行号，本行=非必填
     * @param accountName    转入账户名称
     * @param account    转入账号
     * @param isCrossLine    是否跨(光大)行：01-行内、02-跨行、03-跨行超网、05-银联代付
     * @param brief    摘要
     *
     * @return {fcsSerialNum 资金监管流水号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam take(String memNum, String busiSerialNum, BigDecimal outAmount, String jointLineNum, String accountName, String account, String isCrossLine, String brief){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "type", "4", "memNum",memNum, "busiSerialNum", busiSerialNum, "outAmount", outAmount, "outAccount", cebProperties.getInstAccount(), "jointLineNum",jointLineNum, "accountName",accountName,"account",account,"currency","01","atOnce","2", "isCrossLine", isCrossLine,"brief",brief);
        // 请求
        return request(CebApi.Take, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 会员提现查询
     *
     * @param fcsSerialNum    资金监管流水号（非必填）
     * @param busiSerialNum    商户流水号（非必填）
     * @param memNum    会员编号（非必填）
     * @param startDate    开始时间：YYYYMMDD
     * @param endDate    结束时间：YYYYMMDD
     * @param startNum      起始笔数，从1开始
     * @param queryNum      查询笔数，最大99
     *
     * @return {fcsSerialNum 资金监管流水号}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryTakeResponseParam.ResponseBody.Data> queryTake(String fcsSerialNum, String busiSerialNum, String memNum, String startDate, String endDate, Integer startNum, Integer queryNum){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(), "fcsSerialNum", fcsSerialNum, "busiSerialNum", busiSerialNum, "memNum",memNum, "startNum", startNum, "queryNum", queryNum, "startDate", startDate, "endDate", endDate);
        // 请求
        QueryTakeResponseParam response;
        try {
            response = request(CebApi.QueryTake, XmlUtil.toXml(map), QueryTakeResponseParam.class);
        }catch (BizErrorRuntimeException e){
            if (e.getMessage().contains("查询无记录")) {
                // 无数据
                return new Pager<>();
            }else{
                throw  e;
            }
        }
        // 转换列表
        Pager<QueryTakeResponseParam.ResponseBody.Data> page = new Pager<>();
        page.setTotalCount(response.getBody().getTotalNum());
        page.setList(response.getBody().getFrame());
        return page;
    }

    /**
     * 会员提现查询分页
     *
     * @param fcsSerialNum    资金监管流水号（非必填）
     * @param busiSerialNum    商户流水号（非必填）
     * @param memNum    会员编号（非必填）
     * @param startDate     开始时间：yyyy-MM-dd
     * @param endDate       结束时间：yyyy-MM-dd
     * @param pageNum      页号
     * @param pageSize      页长，最大99
     * @return  pager
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public Pager<QueryTakeResponseParam.ResponseBody.Data> queryTakePage(String fcsSerialNum, String busiSerialNum, String memNum, String startDate, String endDate, Integer pageNum, Integer pageSize) {
        // 初始值
        if (IntegerUtil.isEmpty(pageSize) || pageSize > 99) {
            // 页长：最大99
            pageSize = 99;
        }
        if (IntegerUtil.isEmpty(pageNum)) {
            pageNum = 1;
        }
        LocalDate startTime;
        LocalDate endTime;
        if (StringUtils.isBlank(endDate)) {
            endTime = LocalDate.now().plusDays(1);
        } else {
            endTime = LocalDateUtil.toLocalDate(endDate);
        }
        if (StringUtils.isBlank(startDate)) {
            // 跨度无限制
            startTime = LocalDate.of(2022, 1, 1);
        } else {
            startTime = LocalDateUtil.toLocalDate(startDate);
        }
        // 查询
        Pager<QueryTakeResponseParam.ResponseBody.Data> page = queryTake(fcsSerialNum, busiSerialNum, memNum, LocalDateUtil.toNum(startTime), LocalDateUtil.toNum(endTime), (pageNum - 1) * pageSize + 1, pageSize);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        page.setPageCount(Pager.getPageCount(page.getTotalCount(), pageSize));
        return page;
    }

    /**
     * 会员提现查询
     *
     * @param fcsSerialNum    资金监管流水号（二选一）
     * @param busiSerialNum    商户流水号
     * @return
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public QueryTakeResponseParam.ResponseBody.Data queryTake(String fcsSerialNum, String busiSerialNum) {
        Pager<QueryTakeResponseParam.ResponseBody.Data> page = queryTakePage(fcsSerialNum, busiSerialNum, null, null, null, 1, 1);
        if (!page.getList().isEmpty()) {
            return page.getList().get(0);
        }
        return null;
    }

    /**
     * 平台收费
     *
     * 场景：将会员账户中的资金，收取到商户账户
     *
     * @param memNum        会员编号
     * @param busiSerialNo  商户流水号
     * @param amount    金额
     * @param tradeTime   发生时间
     * @param brief   摘要
     *
     * @return {fcsSerialNum 资金监管流水号, resultCode 处理标记：1-成功、2-失败、3-未处理、0-未明}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam charge(String memNum, String busiSerialNo, BigDecimal amount, LocalDateTime tradeTime, String brief){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"memNum", memNum,"busiSerialNo",busiSerialNo,"currency","01","amount",amount, "tradeTime", LocalDateUtil.format(tradeTime, DateTimeFormatter.ofPattern("yyyyMMddHH:mm:ss")), "brief",brief);
        // 请求
        return request(CebApi.Charge, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 平台转账
     *
     * 1.收入资金划转：商户将虚拟收入户资金转到自有资金户
     * 2.利息转自有资金：商户将虚拟利息户资金转到自有资金户
     * 3.平台提取：转入账户为自有资金账户，转出账户为保证金账户。
     *
     * @param flag  操作类型：2-收入资金划转、4-利息收入划转
     * @param busiSerialNum  商户流水号
     * @param amount  金额
     * @param tradeTime  时间
     * @param isCrossLine  是否跨行：01-行内、02-跨行、03-跨行超网
     * @param targetAccountBankNum  转入账户开户行联行号（行内非必填）
     * @param targetAccountBank  转入账户开户行（总行）
     * @param targetAccountBankName  转入账户开户行全称（支行）
     * @param targetAccountName  转入账户名称
     * @param targetAccount  转入账号
     * @param remark  备注（100字）
     *
     * @return {fcsSerialNum 资金监管流水号, resultCode 处理标记：1-成功、2-失败、3-未处理、0-未明}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam transfer(String flag, String busiSerialNum, BigDecimal amount, LocalDateTime tradeTime, String isCrossLine, String targetAccountBankNum, String targetAccountBank, String targetAccountBankName, String targetAccountName, String targetAccount, String remark){
        // 参数
        MapSo map = MapSo.getInstance("busiNum", cebProperties.getInstId(),"flag",flag,"busiSerialNum",busiSerialNum,"currency","01","amount",amount, "tradeTime", LocalDateUtil.format(tradeTime, DateTimeFormatter.ofPattern("yyyyMMddHH:mm:ss")), "atOnce","1","isCrossLine",isCrossLine,"targetAccountBankNum",targetAccountBankNum,"targetAccountBank",targetAccountBank,"targetAccountBankName",targetAccountBankName,"targetAccountName",targetAccountName,"targetAccount",targetAccount, "sourceAccountName",cebProperties.getInstName(), "sourceAccount",cebProperties.getInstAccount(),"remark",remark);
        // 请求
        return request(CebApi.Transfer, XmlUtil.toXml(map), FcsSerialNumResponseParam.class);
    }

    /**
     * 平台结算（转出资金）
     *
     * 注意：结算分 1收入户 和 2利息户 两种，需要分别查询和提取，不能直接用 0 汇总户 的余额提取
     *
     * @param flag           操作类型：2-收入资金划转、4-利息收入划转
     * @param busiSerialNum  商户流水号
     * @param amount  金额
     * @param tradeTime  时间
     * @param remark  备注（100字）
     *
     * @return {fcsSerialNum 资金监管流水号, resultCode 处理标记：1-成功、2-失败、3-未处理、0-未明}
     *
     * @author 延晓磊
     * @since 2022年02月22日
     */
    public FcsSerialNumResponseParam settle(String flag, String busiSerialNum, BigDecimal amount, LocalDateTime tradeTime, String remark){
        return transfer(flag, busiSerialNum, amount, tradeTime, "01", "", BANK_NAME, cebProperties.getInstSettleAccountBank(), cebProperties.getInstName(), cebProperties.getInstSettleAccount(), remark);
    }




    /**
     * 申请工作密钥
     *
     * @return
     */
    private SoftEncGM applyKey(){
        // 请求密钥
        ApplyKeyResponseParam response = request(CebApi.ApplyKey, "<operationDate>"+LocalDateUtil.toDateNum()+"</operationDate>", ApplyKeyResponseParam.class);
        if (response.getBody().isError()) {
            LOG.warn(CebLogs.REQUEST_ERROR, CebApi.ApplyKey.getName(), response.getBody().getErrorDescription());
            throw new BizErrorRuntimeException(PayMsgs.REQUEST_ERROR + response.getBody().getErrorDescription());
        }
        // 更新密钥
        SoftEncGM softEncGM = new SoftEncGM();
        try {
            // 初始化密钥
            softEncGM.Init(cebProperties.getKeyPath(appProperties.isProd()));
            // 更新密钥.2.SYS(PIN密钥)
            softEncGM.WritePINK(response.getBody().getKeyValue(), response.getBody().getVerifyValue());
            // 更新密钥.3.SYS(MAC密钥)
            softEncGM.WriteMACK(response.getBody().getKeyValue1(), response.getBody().getVerifyValue1());
        } catch (Exception e) {
            LOG.error(CebLogs.REQUEST_FAIL, CebApi.ApplyKey.getName(), "更新密钥库失败", e);
            throw RestRuntimeException.apiError(PayMsgs.REQUEST_FAIL, e);
        }
        this.softEncGM = softEncGM;
        return softEncGM;
    }

    /**
     * 发起请求
     *
     * @param api   接口
     * @param body  请求体
     * @param resultClass  结果类型
     * @return
     */
    private <R extends ResponseParam>R request(CebApi api, String body, Class<R> resultClass){
        return request(api, body, resultClass, true);
    }

    /**
     * 发起请求
     *
     * @param api   接口
     * @param body  请求体
     * @param resultClass  结果类型
     * @param retry        是否允许重试
     * @return
     */
    private <R extends ResponseParam>R request(CebApi api, String body, Class<R> resultClass, boolean retry){
        LOG.info(CebLogs.REQUEST, api.getName());
        // 报文
        String param = buildParam(cebProperties.getInstId(), IDUtil.getSnowflakeId(), LocalDateTime.now(), api.getCode(), body);
        if (!CebApi.ApplyKey.equals(api)) {
            param = addSuffix(param);
        }
        param = addPrefix(param);
        LOG.debug(CebLogs.REQUEST_PARAM, api.getName(), param);
        // 请求
        HttpRequest request = HttpRequest.post(cebProperties.getUrl()).html(param).setContentType("text/html;charset=" + CHARSET.name());
        if (StringUtils.isBlank(cebProperties.getPfxPwd())) {
            request.ssl();
        }else{
            // 签名文件
            request.sslByPfx(IoUtil.getInputStreamByPath(cebProperties.getPfxPath(appProperties.isProd())), cebProperties.getPfxPwd());
        }
        String response;
        try {
            response = HttpUtil.request(request).asString(CHARSET).trim();
        }catch (Exception e){
            LOG.error(CebLogs.REQUEST_FAIL, api.getName(), e.getMessage(), e);
            throw RestRuntimeException.apiError(PayMsgs.REQUEST_FAIL, e);
        }
        // 响应
        LOG.debug(CebLogs.REQUEST_RESULT, api.getName(), response);
        response = response.substring(6);
        if (!response.endsWith(">")) {
            response = response.substring(0, response.length()-16);
        }
        R r = XmlUtil.toObj(response, new XStreamClassAliasParam(resultClass, "in"));
        if (r.getHead().isError()) {
            // 失败
            if (retry && (r.getBody().getErrorInfo().startsWith("MAC0002") || r.getBody().getErrorInfo().startsWith("ERR002"))) {
                // MAC签名失败，刷新MAC码并重新请求
                LOG.info("MAC签名失败，重试请求：" + r.getBody().getErrorInfo());
                applyKey();
                return this.request(api, body, resultClass, false);
            }
            LOG.warn(CebLogs.REQUEST_ERROR, api.getName(), r.getBody().getErrorInfo());
            throw new BizErrorRuntimeException(PayMsgs.REQUEST_ERROR + r.getBody().getErrorInfo());
        }else{
            return r;
        }
    }

    /**
     * 构建报文
     *
     * @param instId    机构号：4位，银行提供
     * @param seqNo     流水号：同一天内去重
     * @param date      交易时间
     * @param tradeCode 交易码
     * @param body      请求体
     * @return
     */
    private static String buildParam(String instId, String seqNo, LocalDateTime date, String tradeCode, String body){
        return "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
                "<in>" +
                    "<head>" +
                        "<version>1.0.1</version>" +
                        "<InstID>0880"+ instId +"</InstID>" +
                        "<trmSeqNum>"+ seqNo +"</trmSeqNum>" +
                        "<tranDate>"+ LocalDateUtil.toNum(date.toLocalDate()) +"</tranDate>" +
                        "<tranTime>"+ LocalDateUtil.toNum(date.toLocalTime()) +"</tranTime>" +
                        "<tradeCode>"+ tradeCode +"</tradeCode>" +
                    "</head>" +
                    "<body>" +
                        StringUtils.trimToEmpty(body) +
                    "</body>" +
                "</in>";
    }

    /**
     * 拼接参数前缀：6位报文长度
     *
     * @param param 请求报文 + MAC值
     * @return
     */
    private static String addPrefix(String param){
        return String.format("%06d", param.getBytes(CHARSET).length)  + param;
    }

    /**
     * 拼接参数后缀：16位报文MAC值
     *
     * @param param 请求报文
     * @return
     */
    private String addSuffix(String param){
        // 获取密钥工具
        if (this.softEncGM == null) {
            applyKey();
        }
        // 计划MAC值
        try {
            return param + this.softEncGM.GenMac(param.getBytes(CHARSET));
        } catch (Exception e) {
            // 重试
            try {
                return param + applyKey().GenMac(param.getBytes(CHARSET));
            } catch (Exception exception) {
                LOG.error(CebLogs.REQUEST_FAIL, "*", "计算MAC码失败", e);
                throw RestRuntimeException.apiError(PayMsgs.REQUEST_FAIL, e);
            }
        }
    }
}
